using System;
using System.Collections.Generic;
using System.ComponentModel;

using SBPweb.Modules.Modularity;

using SBPweb.Contracts;
using SBPweb.Contracts.StateManagement;

namespace SBPweb.Modules.StateManagement
{
	public class StateManagementService : IStateManagementService
	{
		#region Private members

		private StateManagementModule pModule;
		private State pCurrentState;
		private Mode pCurrentMode;
		private List<State> pAvailableStates;
		private Dictionary<Mode, List<State>> pModes;
		private object pStateLockedBy;
		private object pModeLockedBy;
        private string pMessage;

		#endregion

		#region Constructor logic

		public StateManagementService(StateManagementModule asmModule, Dictionary<Mode, List<State>> availableStates)
		{
			pModule = asmModule;
			pModes = availableStates;
			pStateLockedBy = null;
			pModeLockedBy = null;
			pAvailableStates = new List<State>();
			foreach (Mode m in pModes.Keys)
			{
				foreach (State s in pModes[m])
				{
					if (!pAvailableStates.Contains(s))
					{
						pAvailableStates.Add(s);
					}
				}
			}
			foreach (Mode m in pModes.Keys)
			{
				SetMode(this, m, false);
				break;
			}
		}

		#endregion

		#region Public properties

		#region IService members

		public string Name
		{
			get
			{
				return "StateManagementService";
			}
		}

		public IModule Module
		{
			get
			{
				return pModule;
			}
		}

		#endregion

		#region IStateManagementService properties

		public State CurrentState
		{
			get
			{
				return pCurrentState;
			}
		}

		public Mode CurrentMode
		{
			get
			{
				return pCurrentMode;
			}
		}

		public List<State> CurrentStates
		{
			get
			{
				return pModes[pCurrentMode];
			}
		}

		public List<State> AllStates
		{
			get
			{
				return pAvailableStates;
			}
		}

		public System.Collections.Generic.List<Mode> AvailableModes
		{
			get
			{
				return new List<Mode>(pModes.Keys);
			}
		}

		public bool IsStateLocked
		{
			get
			{
				return pStateLockedBy != null;
			}
		}

		public bool IsModeLocked
		{
			get
			{
				return pModeLockedBy != null;
			}
		}

		public Dictionary<Mode, List<State>> Modes
		{
			get
			{
				return pModes;
			}
		}

        public string Message
        {
            get
            {
                return pMessage;
            }
            set
            {
                pMessage = value;

                if (MessageChanged != null)
                {
                    MessageChanged(this, new MessageEventArgs(pMessage));
                }
            }
        }

		#endregion

		#endregion

		#region Public methods

		#region Get state

		public State GetStateByName(string name)
		{
			List<State> states = pModes[pCurrentMode];
			if (states == null)
			{
				return null;
			}
			foreach (State state in states)
			{
				if (state.Name == name)
				{
					return state;
				}
			}
			return null;
		}

		public State GetStateById(int id)
		{
			List<State> states = pModes[pCurrentMode];
			if (states == null)
			{
				return null;
			}
			foreach (State state in states)
			{
				if (state.Id == id)
				{
					return state;
				}
			}
			return null;
		}

		public State GetStateByDirectoryName(string name)
		{
			List<State> states = pModes[pCurrentMode];
			if (states == null)
			{
				return null;
			}
			foreach (State state in states)
			{
				if (state.DirectoryStateName == name)
				{
					return state;
				}
			}
			return null;
		}

		#endregion

		#region Get mode

		public Mode GetModeByName(string name)
		{
			foreach (Mode mode in pModes.Keys)
			{
				if (mode.Name == name)
				{
					return mode;
				}
			}
			return null;
		}

		public Mode GetModeById(int id)
		{
			foreach (Mode mode in pModes.Keys)
			{
				if (mode.Id == id)
				{
					return mode;
				}
			}
			return null;
		}

		#endregion

		#region Set State

		public bool SetState(object sender, State newState, TimeSpan timeout, bool lockState)
		{
			lock (this)
			{
				bool beenLocked = pStateLockedBy != null;
				if (pStateLockedBy != null && pStateLockedBy != sender)
				{
					DateTime endTime = DateTime.Now.Add(timeout).AddMilliseconds(1);
					while (pStateLockedBy != null && pStateLockedBy != sender && DateTime.Now < endTime)
					{
						System.Threading.Thread.Sleep(100);
					}
					if (DateTime.Now > endTime)
					{
						throw new TimeoutException();
					}
				}
				if (pModes[pCurrentMode].Contains(newState))
				{
					if (FireStateChangingEvent(StateChanging, new StateChangingEventArgs(newState, pCurrentState, pStateLockedBy != null, timeout, sender)))
					{
						pStateLockedBy = lockState ? sender : null;
						pCurrentState = newState;

						FireStateEvent(StateChanged, new StateEventArgs(newState, pCurrentState, pStateLockedBy != null, timeout, sender));
						if (!beenLocked && pStateLockedBy != null)
						{
							FireStateEvent(StateLocked, new StateEventArgs(newState, pCurrentState, pStateLockedBy != null, timeout, sender));
						}
						if (beenLocked && pStateLockedBy == null)
						{
							FireStateEvent(StateUnlocked, new StateEventArgs(newState, pCurrentState, pStateLockedBy != null, timeout, sender));
						}
						return true;
					}
					FireStateEvent(StateChangeCancelled, new StateEventArgs(newState, pCurrentState, pStateLockedBy != null, timeout, sender));
				}
				return false;
			}
		}

		public bool SetState(object sender, State newState, bool lockState)
		{
			return SetState(sender, newState, new TimeSpan(0), lockState);
		}

		public bool SetState(object sender, State newState)
		{
			return SetState(sender, newState, new TimeSpan(0), false);
		}

		#endregion

		#region Set mode

		public bool SetMode(object sender, Mode newMode, TimeSpan timeout, bool lockMode)
		{
			lock (this)
			{
				bool beenLocked = pModeLockedBy != null;
				if (pModeLockedBy != null && pModeLockedBy != sender)
				{
					DateTime endTime = DateTime.Now.Add(timeout);
					while (pModeLockedBy != null && pModeLockedBy != sender && DateTime.Now < endTime)
					{
						System.Threading.Thread.Sleep(100);
					}
					if (DateTime.Now > endTime)
					{
						throw new TimeoutException();
					}
				}
				if (pModes.ContainsKey(newMode))
				{

					if (FireModeChangingEvent(ModeChanging, new ModeChangingEventArgs(newMode, pCurrentMode, pModeLockedBy != null, timeout, sender)))
					{

						Mode oldCurrentMode = pCurrentMode;
						Object oldModeLockedBy = pModeLockedBy;

						pModeLockedBy = lockMode ? sender : null;
						pCurrentMode = newMode;

						bool stateChanged = false;
						foreach (State s in pModes[newMode])
						{
							if (s.Name == newMode.DefaultStateName)
							{
								stateChanged = SetState(sender, s, lockMode);
								break;
							}
						}

						if (stateChanged)
						{
							FireModeEvent(ModeChanged, new ModeEventArgs(newMode, pCurrentMode, pModeLockedBy != null, timeout, sender));
							if (!beenLocked && pModeLockedBy != null)
							{
								FireModeEvent(ModeLocked, new ModeEventArgs(newMode, pCurrentMode, true, timeout, sender));
							}
							if (beenLocked && pModeLockedBy == null)
							{
								FireModeEvent(ModeUnlocked, new ModeEventArgs(newMode, pCurrentMode, false, timeout, sender));
							}
							return true;
						}

						pModeLockedBy = oldModeLockedBy;
						pCurrentMode = oldCurrentMode;
					}
					FireModeEvent(ModeChangeCancelled, new ModeEventArgs(newMode, pCurrentMode, false, timeout, sender));
				}
				return false;
			}
		}

		public bool SetMode(object sender, Mode newMode, bool lockMode)
		{
			return SetMode(sender, newMode, new TimeSpan(0), lockMode);
		}

		public bool SetMode(object sender, Mode newMode)
		{
			return SetMode(sender, newMode, new TimeSpan(0), false);
		}

		#endregion

		#region Event fireing

		private void FireStateEvent(EventHandler<StateEventArgs> eventhandler, StateEventArgs e)
		{
			if (eventhandler != null)
			{
				Delegate[] delegates = eventhandler.GetInvocationList();
				foreach (Delegate d in delegates)
				{
					try
					{
						d.DynamicInvoke(new object[] { this, e });
					}
					catch (Exception ex)
					{
						Module.Host.LogManager.WriteError(this, string.Format("Exception while invoking delegate on {0} - {1}", eventhandler.Target.GetType().ToString(), ex.ToString()), "StateManagement");
					}
				}
			}
		}

		private bool FireStateChangingEvent(EventHandler<StateChangingEventArgs> eventhandler, StateChangingEventArgs e)
		{
			if (eventhandler != null)
			{
				Delegate[] delegates = eventhandler.GetInvocationList();
				foreach (Delegate d in delegates)
				{
					try
					{
						e.Cancel = false;
						d.DynamicInvoke(new object[] { this, e });
						if (e.Cancel)
						{
							Module.Host.LogManager.WriteVerbose(this, string.Format("Event ({0}) cancelled by {1}", eventhandler.ToString(), eventhandler.Target.GetType().ToString()), "StateManagement");
							return false;
						}
					}
					catch (Exception ex)
					{
						Module.Host.LogManager.WriteError(this, string.Format("Exception while invoking delegate on {0} - {1}", eventhandler.Target.GetType().ToString(), ex.ToString()), "StateManagement");
					}
				}
			}
			return true;
		}

		private void FireModeEvent(EventHandler<ModeEventArgs> eventhandler, ModeEventArgs e)
		{
			if (eventhandler != null)
			{
				Delegate[] delegates = eventhandler.GetInvocationList();
				foreach (Delegate d in delegates)
				{
					try
					{
						d.DynamicInvoke(new object[] { this, e });
					}
					catch (Exception ex)
					{
						Module.Host.LogManager.WriteError(this, string.Format("Exception while invoking delegate on {0} - {1}", eventhandler.Target.GetType().ToString(), ex.ToString()), "StateManagement");
					}
				}
			}
		}

		private bool FireModeChangingEvent(EventHandler<ModeChangingEventArgs> eventhandler, ModeChangingEventArgs e)
		{
			if (eventhandler != null)
			{
				Delegate[] delegates = eventhandler.GetInvocationList();
				foreach (Delegate d in delegates)
				{
					try
					{
						e.Cancel = false;
						d.DynamicInvoke(new object[] { this, e });
						if (e.Cancel)
						{
							Module.Host.LogManager.WriteVerbose(this, string.Format("Event ({0}) cancelled by {1}", eventhandler.ToString(), eventhandler.Target.GetType().ToString()), "StateManagement");
							return false;
						}
					}
					catch (Exception ex)
					{
						Module.Host.LogManager.WriteError(this, string.Format("Exception while invoking delegate on {0} - {1}", eventhandler.Target.GetType().ToString(), ex.ToString()), "StateManagement");
					}
				}
			}
			return true;
		}

		#endregion

		#endregion

		#region Events

		public event EventHandler<StateChangingEventArgs> StateChanging;
		public event EventHandler<StateEventArgs> StateChangeCancelled;
		public event EventHandler<StateEventArgs> StateChanged;
		public event EventHandler<StateEventArgs> StateLocked;
		public event EventHandler<StateEventArgs> StateUnlocked;

		public event EventHandler<ModeChangingEventArgs> ModeChanging;
		public event EventHandler<ModeEventArgs> ModeChangeCancelled;
		public event EventHandler<ModeEventArgs> ModeChanged;
		public event EventHandler<ModeEventArgs> ModeLocked;
		public event EventHandler<ModeEventArgs> ModeUnlocked;

        public event EventHandler<MessageEventArgs> MessageChanged;

		#endregion
	}
}
